package com.lmaye.cloud.starter.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.util.CollectionUtils;

import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * -- CacheConfig
 *
 * @author lmay
 * @since 2024/9/24 11:56
 */
@EnableCaching
@Configuration
@AllArgsConstructor
@ConditionalOnProperty(value = "caching.enabled", prefix = "web", matchIfMissing = true)
public class CachingConfig {
    /**
     * Caching Properties
     */
    private final CachingProperties cachingProperties;

    /**
     * @param factory RedisConnectionFactory
     * @return RedisCacheManager
     */
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(factory),
                this.getCacheConfigWithTtl(cachingProperties.getDefaultTtl()), this.getCacheConfigCustom());
    }

    /**
     * 指定 key 策略
     *
     * @return Map<String, RedisCacheConfiguration>
     */
    private Map<String, RedisCacheConfiguration> getCacheConfigCustom() {
        Map<String, RedisCacheConfiguration> map = new HashMap<>();
        List<String> keyAndTtlList = cachingProperties.getCustomKeyTtl();
        if (!CollectionUtils.isEmpty(keyAndTtlList)) {
            keyAndTtlList.forEach(it -> {
                String[] keyAndTtl = it.split(cachingProperties.getSplitStrategy());
                map.put(keyAndTtl[0], this.getCacheConfigWithTtl(Integer.parseInt(keyAndTtl[1])));
            });
        }
        return map;
    }

    /**
     * 默认策略，未配置key
     *
     * @param seconds 过期时间
     * @return RedisCacheConfiguration
     */
    private RedisCacheConfiguration getCacheConfigWithTtl(Integer seconds) {
        Jackson2JsonRedisSerializer<Object> redisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        // 添加 JavaTimeModule 支持
        mapper.registerModule(new JavaTimeModule());
        // 禁用将日期写为时间戳
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        redisSerializer.setObjectMapper(mapper);
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
        configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(redisSerializer)).entryTtl(Duration.ofSeconds(seconds));
        return configuration;
    }
}
